﻿//#define DEBUG_CHECK
#define RELEASE_L
using System;

using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using System.Drawing.Imaging;
using Algorithms_10.Deformation;

namespace Vision
{
    /// <summary>
    /// All neccesary functions for Quadriletarl Distortion Algorithm.
    /// Must define DEBUG_L or RELEASE_L, not Both!!!!
    /// 
    /// Author: Luca Gobbo
    /// </summary>
    public static class QuadrilateralDistortion
    {
        /// <summary>
        /// First attempt to test the algorithm, not working yet
        /// 
        /// Author: Luca Gobbo
        /// </summary>
        /// <param name="source"></param>
        /// <returns></returns>
        public static Bitmap run(Bitmap source)
        {

            #region TestData points
            Point A = new Point(4, 0);
            Point B = new Point(8, 1);
            Point C = new Point(9, 5);
            Point D = new Point(0, 8);
            #endregion



            BitmapData sourceData = source.LockBits(new Rectangle(0, 0, source.Width, source.Height), ImageLockMode.WriteOnly, PixelFormat.Format24bppRgb);
            Bitmap returnBitmap = new Bitmap(source.Width, source.Height);
            BitmapData returnData = returnBitmap.LockBits(new Rectangle(0, 0, source.Width, source.Height), ImageLockMode.WriteOnly, PixelFormat.Format24bppRgb);

            int total = sourceData.Stride * source.Height;
            int c = sourceData.Stride / sourceData.Width;
            unsafe
            {
                byte* srcPointer = (byte*)sourceData.Scan0;
                byte* retPointer = (byte*)returnData.Scan0;

                //make white background
                int ii = 0;
                while (ii < total)
                    retPointer[ii++] = 255;



                for (int i = 0; i < total; i += c)
                {
                    int y = i % sourceData.Stride;
                    int x = i / sourceData.Stride;
#if RELEASE_L
                    Point newPoint = calculatePoint(A, B, C, D, new Point(x, y));
                    if (newPoint.X >= 0 && newPoint.Y >= 0)
                    {
                        int n = newPoint.X * sourceData.Stride + newPoint.Y;
                        if (n > 0 && n < total)
                        {
                            retPointer[n] = 0;
                            retPointer[n + 1] = 0;
                            retPointer[n + 2] = 0;
                        }
                    }

                }
            }

#endif
#if DEBUG_CHECK
                    int n = x * sourceData.Stride + y;
                    retPointer[n] = srcPointer[i];
                    retPointer[n + 1] = srcPointer[i + 1];
              
                }
            }
#endif

            source.UnlockBits(sourceData);
            returnBitmap.UnlockBits(returnData);

            return returnBitmap;
        }

        /// <summary>
        /// Makes an sqaure from an Quadrilateral
        /// The actual Quadriletarl Distortion Algorithm,
        /// Works on mathmatical level,
        /// 
        /// Explaination on:  http://ryoushin.com/cmerighi/en-us/2006-09-30_21/quadrilateral_distortion_algorithm,
        /// 
        /// Author: Luca Gobbo
        /// </summary>
        /// <param name="A">Left Top Point</param>
        /// <param name="B">Right Top Point</param>
        /// <param name="C">Right Bottom Point</param>
        /// <param name="D">Left Bottom Point</param>
        /// <returns>Point of calculated Point</returns>
        public static Point calculatePoint(Point A, Point B, Point C, Point D, Point P)
        {
            /* ========= Step 1 =========
             * 
             * Optimization ideas:
             *      Step 1 could be precalculated for increased preformance, there is no need for Point P
             * 
             * O » intersecting the lines through A' and B' and through D' and C';
             * N » intersecting the lines through A' and D' and thorugh B' and C'.
             */
            Line AB = calcLineVariables(A, B);
            Line DC = calcLineVariables(D, C);
            Line AD = calcLineVariables(A, D);
            Line BC = calcLineVariables(B, C);

            // if one of the lines is parallel, they can't intersect, this means, letter shouldn't be deformed;
            if (calculateDelta(AB, DC) == 0)
                throw new ParallelLinesException("Lines AB: " + A + " " + B + " & DC " + D + " " + C + "are Parallel");
            if (calculateDelta(AD, BC) == 0)
                throw new ParallelLinesException("Lines AD: " + A + " " + D + " & BC: " + B + " " + C + "  are Parallel");


            Point O = getIntersection(AB, DC);
            Point N = getIntersection(AD, BC);

            /* ========= Step 2 =========
             * 
             * L » intersecting the line through O and P and the line through A' and D';
             * M » intersecting the line through O and P and the line through B' and C';
             * J » intersecting the line through N and P and the line through A' and B';
             * K » intersecting the line through N and P and the line through C' and D';
             */
            Line OP = calcLineVariables(O, P);
            Line NP = calcLineVariables(N, P);
            Line CD = calcLineVariables(C, D);

            Point L = getIntersection(OP, AD);
            Point M = getIntersection(OP, BC);
            Point J = getIntersection(NP, AB);
            Point K = getIntersection(NP, CD);

            /* ========= Step 3 =========
             * 
             * dJP » distance between J and P
             * dKP » distance between K and P
             * dLP » distance between L and P
             * dMP » distance between M and P
             */

            int dJP = P.Y - J.Y;
            int dKP = K.Y - P.Y;
            int dLP = P.X - L.X;
            int dMP = M.X - P.X;

            /* ========= Step 3 =========
             * 
             * In the Cartesian orthogonal monometric coordinate system Axy we have:
             * yP0 = yD * dJP / (dJP + dKP)
             * xP0 = xB * dLP / (dLP + dMP)
             */

            int x_new = D.Y * dJP / (dJP + dKP);
            int y_new = C.X * dLP / (dLP + dMP);

            return new Point(x_new, y_new);
        }

        #region Actual Intersection functions
        /// a struct representing a Line,
        /// Line defined as Ax + By = C,
        /// 
        /// Author: Luca Gobbo
        /// </summary>
        private struct Line
        {
            public float A, B, C;

            public Line(float A, float B, float C)
            {
                this.A = A;
                this.B = B;
                this.C = C;
            }

            public override string ToString()
            {
                return "A= " + A + ", B=" + B + ", C=" + C;
                //return base.ToString();
            }
        }

        /// <summary>
        /// Calculate the ABC values of an line,
        /// Line defined as Ax + By = C,
        /// these values are used to calculate an intersection Point,
        /// 
        /// Author: Luca Gobbo
        /// </summary>
        /// <param name="p1"></param>
        /// <param name="p2"></param>
        /// <returns>A line struct</returns>
        private static Line calcLineVariables(Point p1, Point p2)
        {
            float A = p2.Y - p1.Y;
            float B = p1.X - p2.X;
            float C = A * p1.X + B * p1.Y;
#if DEBUG_L
            System.Console.WriteLine("\nA: " + p2.Y + " - " + p1.Y + " = " + A);
            System.Console.WriteLine("B: " + p1.X + " - " + p2.X + " = " + B);
            System.Console.WriteLine("C: " + A + " * " + p1.X + " + " + B + " * " + p1.Y + " = " + C);
#endif

            return new Line(A, B, C); ;
        }

        private static float calculateDelta(Line line1, Line line2)
        {
            float delta = delta = line1.A * line2.B - line2.A * line1.B;
            return delta;
        }

        /// <summary>
        /// Gets the intersection between two lines.
        /// 
        /// Calculates the intersection point between to Line structs,
        /// Throws exception when delta equals 0, meaning the lines are parralel and won't intersect.
        /// 
        /// Author: Luca Gobbo
        /// </summary>
        /// <param name="line1">Line struct</param>
        /// <param name="line2">Line struct</param>
        /// <returns>Point with coordinates of the intersection, Converted to Integer to round the coordinates</returns>
        private static Point getIntersection(Line line1, Line line2)
        {
            float delta = calculateDelta(line1, line2);
            if (delta == 0)
                throw new ParallelLinesException("Lines are parralel, line1: " + line1.ToString() + ", line2: " + line2.ToString());

            float x = (line2.B * line1.C - line1.B * line2.C) / delta;
            float y = (line1.A * line2.C - line2.A * line1.C) / delta;

#if DEBUG_L
            System.Console.WriteLine("\nResult in floats : {" + x + "},{" + y + "}");
#endif
            return new Point((int)x, (int)y);
        }

        /// <summary>
        /// Wrapper function, creates two lines from given points,
        /// tries to intersect these lines.
        /// Author: Luca Gobbo
        /// </summary>
        /// <param name="l1p1">Point 1 for line 1</param>
        /// <param name="l1p2">Point 2 for line 1</param>
        /// <param name="l2p1">Point 1 for line 2</param>
        /// <param name="l2p2">Point 2 for line 2</param>
        /// <returns>Point with coordinates of the intersection from the given points</returns>
        public static Point intersect(Point l1p1, Point l1p2, Point l2p1, Point l2p2)
        {

            Line line1 = QuadrilateralDistortion.calcLineVariables(l1p1, l1p2);
            Line line2 = QuadrilateralDistortion.calcLineVariables(l2p1, l2p2);
            Point p = QuadrilateralDistortion.getIntersection(line1, line2);
#if DEBUG_L
            // DEBUG_LToConsole(l1p1, l1p2, l2p1, l2p2, p);
#endif


            return p;
        }
        #endregion

        #region debugging functions, not used annymore

        /// <summary>
        /// Obsolete,
        /// used to check if coordinates where mapped correctly when using intersect function.
        /// 
        /// Author: Luca Gobbo
        /// </summary>
        /// <param name="l1p1"></param>
        /// <param name="l1p2"></param>
        /// <param name="l2p1"></param>
        /// <param name="l2p2"></param>
        /// <param name="p"></param>
        [Obsolete("Only for debugging purposes of one Point, dont use!")]
        private static void debugToConsole(Point l1p1, Point l1p2, Point l2p1, Point l2p2, Point p)
        {
            String debug = "";
            for (int y = -10; y < 10; y++)
            {

                for (int x = -10; x < 10; x++)
                {


                    if (checkPointDebug(x, y, l1p1)) { debug += "A"; }
                    else if (checkPointDebug(x, y, l1p2)) { debug += "D"; }
                    else if (checkPointDebug(x, y, l2p1)) { debug += "B"; }
                    else if (checkPointDebug(x, y, l2p2)) { debug += "C"; }
                    else if (checkPointDebug(x, y, p)) { debug += "*"; }
                    else if (y == 0)
                    {
                        int t = x % 10;
                        if (t < 0) t = -t;
                        debug += t;
                    }
                    else if (x == 0)
                    {
                        int t = y % 10;
                        if (t < 0) t = -t;
                        debug += t;
                    }
                    else debug += " ";
                }

                debug += "\n";
            }
            System.Console.Write(debug);

        }
        /// <summary>
        /// Obsolete,         
        /// Check if point matches given x and y coordinates.
        /// 
        /// Author: Luca Gobbo
        /// </summary>
        /// <param name="x"></param>
        /// <param name="y"></param>
        /// <param name="p"></param>
        /// <returns></returns>
        [Obsolete("Only for debugging purposes of one Point, dont use!")]
        private static bool checkPointDebug(int x, int y, Point p)
        {
            return p.X == x && p.Y == y;
        }

        #endregion


    }
}
